import { randomStr } from '@/utils/randomStr'

/**
 * 网页使用zoom方案适配windows系统放大125%，150%
 * 图形绘制需要获得网页缩放比例矫正拖拽坐标
 */
let zoom = document.body.style.zoom

const useDraw = () => {
  let Container = null
  let canvas = null // canvas标签
  let ctx = null // canvas画布
  let canvasRes = null // canvas标签
  let ctxRes = null // canvas画布
  let w, h // 画布宽度
  let roomAddable = true // 是否处于编辑模式
  let points = [] // 已加入的点
  let pointsArr = [] // 已加入的区域点集合
  const colors = [
    { stroke: '#0BC2D0', fill: 'rgba(11,194,208, 0.1)' },
    { stroke: '#8988E8', fill: 'rgba(137,136,232, 0.1)' },
    { stroke: '#D187E8', fill: 'rgba(209,135,232, 0.1)' },
    { stroke: '#E8879B', fill: 'rgba(232,135,155, 0.1)' },
    { stroke: '#E99787', fill: 'rgba(233,151,135, 0.1)' },
    { stroke: '#E8C587', fill: 'rgba(232,197,135, 0.1)' },
    { stroke: '#BDDF74', fill: 'rgba(189,223,116, 0.1)' },
    { stroke: '#88E8AF', fill: 'rgba(136,232,175, 0.1)' },
    { stroke: '#87CFE8', fill: 'rgba(135,207,232, 0.1)' }
  ]

  // 默认样式
  const defaultStyle = {
    lineWidth: 2, // 画笔大小
    strokeStyle: '#0BC2D0', // 画笔颜色
    fillStyle: 'rgba(11,194,208, 0.1)', // 填充颜色
    backgroudColor: 'transparent' // 画布颜色
  }

  // 默认选项
  let options = {
    onComplete: undefined, // 绘图完成后的回调
    onError: undefined, // 报错时回调
    beforeComplete: (points) => points.length >= 3, // 完成绘图前的验证
    onClear: undefined, // 清空绘图后的回调
    max: 20, // 限制最多点位数量
    canvasStyle: { ...defaultStyle }
  } // 选项

  // 画横线
  const drawLine = (colorsIndex, mouse) => {
    ctx.clearRect(0, 0, w, h)
    const firstPoint = points[0]
    ctx.beginPath()
    ctx.strokeStyle = colors[colorsIndex].stroke
    ctx.fillStyle = colors[colorsIndex].fill
    ctx.moveTo(firstPoint.x, firstPoint.y)
    ctx.lineTo(mouse.x, mouse.y)
    ctx.stroke()
  }

  // 画区域
  const drawArea = (context, partPoints, colorsIndex, mouse) => {
    // 鼠标创建时
    if (mouse) {
      context.clearRect(0, 0, w, h)
    }
    context.beginPath()
    context.strokeStyle = colors[colorsIndex].stroke
    context.fillStyle = colors[colorsIndex].fill
    // 画已经加上的点
    partPoints.forEach((item, i) => {
      if (i === 0) {
        context.moveTo(item.x, item.y)
      } else {
        context.lineTo(item.x, item.y)
      }
    })

    // 如果传入了鼠标点，将它作为最后一点，动态绘制区域。否则根据points直接封闭区域
    if (mouse) {
      context.lineTo(mouse.x, mouse.y)
      // 回到第一个点，封闭区域
      context.lineTo(partPoints[0].x, partPoints[0].y)
    }

    // 划线与填充
    context.stroke()
    context.fill()
    context.closePath()
  }

  // 根据情况绘制图形
  const draw = (mouse) => {
    const pointLength = points.length
    switch (pointLength) {
      case 0:
        return
      case 1:
        drawLine(pointsArr.length % colors.length, mouse) // 画直线
        break
      default:
        drawArea(ctx, points, pointsArr.length % colors.length, mouse) // 画区域
        break
    }
  }

  // 移动鼠标的事件：动态绘制图形
  const onMouseMove = (e) => {
    draw({ x: e.offsetX / zoom, y: e.offsetY / zoom })
  }

  // 点击事件：添加点
  const onClick = async(e) => {
    zoom = document.body.style.zoom
    if (getOpts().max && points.length > getOpts().max) {
      return
    }

    // 闭合图形: 最后一个点距离起点小于5
    if (points.length > 2 && Math.abs(points[0].x - e.offsetX) <= 5 / zoom && Math.abs(points[0].y - e.offsetY) <= 5 / zoom) {
    // if (points.length > 2 && Math.pow(points[0].x - e.offsetX, 2) + Math.pow(points[0].y - e.offsetY, 2) <= 25) {
      // 图形闭合前校验
      await closedCheck()
      points.push(points[0])
      pointsArr.push({ name: randomStr(), id: randomStr(), points })
      ctx.clearRect(0, 0, w, h)
      ctxRes.clearRect(0, 0, w, h)
      drawAreaByPoints(ctxRes, pointsArr)
      points = []
      options.onComplete && options.onComplete(getPoints())
      return
    }
    points.push({ x: e.offsetX / zoom, y: e.offsetY / zoom })
  }

  const closedCheck = () => {
    return new Promise((resolve, reject) => {
      if (!checkPointCross(points[0], points)) {
        options.onError && options.onError('闭合图形时发生横穿线，请重新绘制！')
        return reject()
      }

      // TODO 面与面交叉校验

      // if (!checkPointConcave(points[0], points, true)) {
      //   options.onError && options.onError('闭合图形时出现凹多边形，请重新绘制！')
      //   return reject()
      // }
      return resolve()
    })
  }

  // 鼠标右键：撤销
  const onRightClick = (e) => {
    // 防止默认打开菜单s
    e.stopPropagation()
    e.preventDefault()

    const pointLength = points.length
    if (pointLength === 0) return
    points.pop()
    if (!points.length) {
      ctx.clearRect(0, 0, w, h)
    } else {
      draw({ x: e.offsetX / zoom, y: e.offsetY / zoom })
    }
    return false
  }

  // 回车事件：完成绘图
  const onEnter = async(e) => {
    const keyCode = e.keyCode || e.which || e.charCode
    if (keyCode === 13) {
      // 至少需要三个点才允许画区域
      if (options.beforeComplete(points)) {
        // 图形闭合前校验
        await closedCheck()
        points.push(points[0])
        pointsArr.push({ name: randomStr(), id: randomStr(), points })
        ctx.clearRect(0, 0, w, h)
        ctxRes.clearRect(0, 0, w, h)
        drawAreaByPoints(ctxRes, pointsArr)
        points = []
        options.onComplete && options.onComplete(getPoints())
      }
    } else if (keyCode === 27) {
      onClear()
    }
  }

  // 清理画布
  const onClear = () => {
    points = []
    ctx.clearRect(0, 0, w, h)
    options.onClear && options.onClear()
  }

  const listen = () => {
    canvas.addEventListener('mousemove', onMouseMove)
    canvas.addEventListener('click', onClick)
    canvas.addEventListener('keydown', onEnter)
    canvas.addEventListener('contextmenu', onRightClick)
    canvas.addEventListener('dblclick', onClear)
  }

  const destroy = () => {
    if (canvas) {
      canvas.removeEventListener('mousemove', onMouseMove)
      canvas.removeEventListener('click', onClick)
      canvas.removeEventListener('keydown', onEnter)
      canvas.removeEventListener('contextmenu', onRightClick)
      canvas.removeEventListener('dblclick', onClear)
    }
    if (canvasRes) {
      canvasRes.removeEventListener('mousedown', canvasResMousedown)
      canvasRes.removeEventListener('mousemove', canvasResMousemove)
      canvasRes.removeEventListener('mouseup', canvasResMouseup)
      canvasRes.removeEventListener('contextmenu', canvasResRightClick)
    }
    canvas = null
    canvasRes = null
    Container = null
    ctx = null
    ctxRes = null
    options = null
  }
  /** ***************** 处理图形编辑  start ****************************** */
  // 编辑图形相关
  let editingArea = {}
  let dragPointIndex = -1
  let isMousedown = false
  let startPos = {}
  let isInPolygon = false // 是否在多边形内
  let cachePointsArr = []
  // 根据点画区域
  const drawAreaByPoints = (context, newPoints) => {
    newPoints.forEach((x, i) => {
      if (x.points.length < 3) {
        return
      }
      pointsArr = newPoints
      // 如果是需要编辑的图形(x.readonly === false) 绘制顶点
      if (x.readonly === false) {
        // 记录当前顶点数据
        cachePointsArr = [...x.points]
        editingArea = { ...x }
        ctxResListen()
      }
      render(context, x, i % colors.length)
    })
  }

  // ctxRes事件绑定
  const ctxResListen = () => {
    canvasRes.addEventListener('mousedown', canvasResMousedown)
    canvasRes.addEventListener('mousemove', canvasResMousemove)
    canvasRes.addEventListener('mouseup', canvasResMouseup)
    canvasRes.addEventListener('contextmenu', canvasResRightClick)
  }

  const canvasResMousedown = e => {
    zoom = document.body.style.zoom
    isMousedown = true
    const { x, y } = toCanvasPos(e)
    dragPointIndex = checkPointIndex(x / zoom, y / zoom)
    // 记录按下的起始位置
    startPos.x = x
    startPos.y = y
    // 判断是否在图形中
    isInPolygon = checkInPolygon(x, y)
  }

  function canvasResMousemove(e) {
    if (!isMousedown) return
    const { x, y } = toCanvasPos(e)
    // 实时更新当前拖动的顶点位置
    if (isMousedown && dragPointIndex !== -1) {
      e.stopPropagation()
      // 是虚拟顶点，转换成真实顶点
      if (editingArea.points[dragPointIndex].fictitious) {
        delete editingArea.points[dragPointIndex].fictitious
      }
      dragPointIndex = getTrueIndex(dragPointIndex)
      removeFictitiousPoints()
      // 删除原来的点插入新的点
      editingArea.points.splice(dragPointIndex, 1, {
        ...editingArea.points[dragPointIndex],
        x,
        y
      })
    } else if (isMousedown && dragPointIndex === -1 && isInPolygon) {
      // 整体移动
      e.stopPropagation()
      const diffX = x - startPos.x
      const diffY = y - startPos.y
      editingArea.points = cachePointsArr.map((item) => {
        return {
          ...item, // 不要把fictitious状态给丢了
          x: item.x + diffX,
          y: item.y + diffY
        }
      })
    }

    edittingDrawerArea()
  }

  // 获取顶点真实索引
  function getTrueIndex(index) {
    let prevFictitiousCount = 0
    for (let i = 0; i < index; i++) {
      if (editingArea.points[i].fictitious) {
        prevFictitiousCount++
      }
    }
    return index - prevFictitiousCount
  }

  function canvasResMouseup(e) {
    isMousedown = false
    dragPointIndex = -1
    cachePointsArr = []
    options.onComplete && options.onComplete(getPoints())
  }

  function canvasResRightClick(e) {
    // 防止默认打开菜单s
    e.stopPropagation()
    e.preventDefault()
    const { x, y } = toCanvasPos(e)
    dragPointIndex = checkPointIndex(x, y)
    // 仅双击真实节点才删除
    if (!editingArea.points[dragPointIndex].fictitious) {
      editingArea.points.splice(dragPointIndex, 1)
      edittingDrawerArea()
    }
  }

  function edittingDrawerArea() {
    const index = pointsArr.findIndex(x => x.id === editingArea.id)
    pointsArr.splice(index, 1, editingArea)
    ctxRes.clearRect(0, 0, w, h)
    pointsArr.forEach((x, i) => {
      if (x.points.length < 3) {
        return
      }
      render(ctxRes, x, i % colors.length)
    })
  }
  function render(context, areaInfo, colorsIndex) {
    const { readonly } = areaInfo
    if (readonly === false) {
      // 先去掉之前插入的虚拟顶点
      areaInfo.points = areaInfo.points.filter((item) => {
        return !item.fictitious
      })
      if (!isMousedown) { // 插入虚拟顶点
        insertFictitiousPoints(areaInfo)
      }
    }
    // 顶点连线
    context.beginPath()
    context.strokeStyle = colors[colorsIndex].stroke
    context.fillStyle = colors[colorsIndex].fill
    areaInfo.points.forEach((item, index) => {
      if (index === 0) {
        context.moveTo(item.x, item.y)
      } else {
        context.lineTo(item.x, item.y)
      }
    })
    context.closePath()
    context.stroke()
    context.fill()
    // 绘制顶点的圆形，仅readonly === false需要绘制
    if (readonly === false) {
      renderPeak(context, areaInfo.points, colorsIndex)
    }
  }

  // 绘制顶点
  const renderPeak = (context, points, colorsIndex) => {
    // 因为要重新设置绘图样式，为了不影响线段，所以需要保存一下绘图状态
    context.save()
    context.lineWidth = 2
    points.forEach(x => {
      // 是中间虚拟节点
      if (x.fictitious) {
        context.strokeStyle = '#fff'
        context.fillStyle = colors[colorsIndex].stroke
      } else {
        context.strokeStyle = colors[colorsIndex].stroke
        context.fillStyle = '#fff'
      }
      context.beginPath()
      context.arc(x.x, x.y, 4, 0, 2 * Math.PI)
      context.fill()
      context.stroke()
    })
    // 恢复绘图状态
    context.restore()
  }

  function toCanvasPos(e) {
    const { left, top } = canvasRes.getBoundingClientRect()
    return {
      x: (e.clientX / zoom - left),
      y: (e.clientY / zoom - top)
    }
  }

  // 检测是否在某个顶点内, 返回顶点下标
  function checkPointIndex(x, y) {
    let result = -1
    // 遍历顶点绘制圆形路径，和上面的绘制顶点圆形的区别是这里不需要实际描边和填充，只需要路径
    editingArea.points.forEach((item, index) => {
      ctxRes.beginPath()
      ctxRes.arc(item.x / zoom, item.y / zoom, 6, 0, 2 * Math.PI)
      // 检测是否在当前路径内
      if (ctxRes.isPointInPath(x, y)) {
        result = index
      }
    })
    return result
  }

  // 检查是否在多边形内
  function checkInPolygon(x, y) {
    // 绘制并闭合路径，不实际描边
    ctxRes.beginPath()
    editingArea.points.forEach((item, index) => {
      if (index === 0) {
        ctxRes.moveTo(item.x, item.y)
      } else {
        ctxRes.lineTo(item.x, item.y)
      }
    })
    ctxRes.closePath()
    return ctxRes.isPointInPath(x, y)
  }
  // 插入虚拟节点
  function insertFictitiousPoints(areaInfo) {
    if (isMousedown) {
      return
    }
    // 生成虚拟顶点，跟创建线段一样的逻辑，只是计算的是线段的中点位置
    const points = []
    const arr = [...editingArea.points]
    const len = arr.length
    for (let i = 0; i < len - 1; i++) {
      const p1 = arr[i]
      const p2 = arr[i + 1]
      points.push({
        x: (p1.x + p2.x) / 2,
        y: (p1.y + p2.y) / 2,
        fictitious: true// 这个字段标志是否是虚拟顶点
      })
    }
    points.push({
      x: (arr[len - 1].x + arr[0].x) / 2,
      y: (arr[len - 1].y + arr[0].y) / 2,
      fictitious: true
    })

    // 插入到顶点数组里
    const newArr = []
    for (let i = 0; i < editingArea.points.length; i++) {
      newArr.push(editingArea.points[i])
      if (i !== editingArea.points.length - 1) {
        newArr.push(points.shift())
      } else {
        const firstPoint = editingArea.points[0]
        const lastPoint = editingArea.points[editingArea.points.length - 1]
        if (firstPoint.x !== lastPoint.x && firstPoint.y !== lastPoint.y) {
          newArr.push(points.shift())
        }
      }
    }
    editingArea.points = [...newArr]
    areaInfo.points = [...newArr]
  }

  function removeFictitiousPoints() {
    editingArea.points = editingArea.points.filter((item) => {
      return !item.fictitious
    })
  }

  /** ***************** 处理图形编辑  end****************************** */

  const init = (dom, newOpts = {}) => {
    // 初始化容器
    Container = dom
    options = {
      ...options,
      ...newOpts,
      canvasStyle: {
        ...defaultStyle,
        ...(newOpts.canvasStyle ? { ...newOpts.canvasStyle } : {})
      }
    }
    roomAddable = newOpts.roomAddable
    w = Container.getBoundingClientRect().width
    h = Container.getBoundingClientRect().height

    const { lineWidth, strokeStyle, fillStyle } = options.canvasStyle
    // 创建结果canvas标签
    canvasRes = document.createElement('canvas')
    initCanvasStyle(canvasRes)
    // 将canvas加入到dom中
    Container.appendChild(canvasRes)
    // 初始化画布
    ctxRes = canvasRes.getContext('2d')
    ctxRes.lineWidth = lineWidth

    // 判断有没有初始值，有的话关闭绘制状态
    if (options.initPoints && options.initPoints.length !== 0) {
      drawAreaByPoints(ctxRes, options.initPoints)
    }

    // 创建编辑canvas标签
    canvas = document.createElement('canvas')
    canvas.tabIndex = '0' // 否则用不了回车
    initCanvasStyle(canvas)
    canvas.focus()
    // 将canvas加入到dom中
    Container.appendChild(canvas)

    // 初始化画布
    ctx = canvas.getContext('2d')
    ctx.lineWidth = lineWidth
    ctx.setLineDash([3, 3])
    ctx.lineDashOffset = -50

    // 注册监听事件
    listen()
    canvas.style.pointerEvents = roomAddable ? 'all' : 'none'
  }

  // 设置canvas标签通用样式
  const initCanvasStyle = (canvasDom) => {
    const { backgroudColor } = options.canvasStyle
    canvasDom.style.position = 'absolute'
    canvasDom.style.left = 0
    canvasDom.style.top = 0
    canvasDom.style.backgroundColor = backgroudColor
    canvasDom.width = w
    canvasDom.height = h
    canvasDom.style.outline = 'none'
    canvasDom.style.userSelect = 'none'
  }

  const getPoints = () => {
    const tempPointsArr = [...pointsArr]
    if (editingArea && editingArea.id) {
      const index = tempPointsArr.findIndex(x => x.id === editingArea.id)
      const tempEditingArea = { ...editingArea }
      tempEditingArea.points = tempEditingArea.points.filter(x => !x.fictitious)
      tempPointsArr.splice(index, 1, tempEditingArea)
    }
    return tempPointsArr
  }
  const getOpts = () => options
  const getCtxRes = () => ctxRes
  const ctxResClear = () => {
    ctxRes.clearRect(0, 0, w, h)
    points = []
    pointsArr = []
  }
  function updateAddable(val) {
    editingArea = {}
    dragPointIndex = -1
    isMousedown = false
    startPos = {}
    isInPolygon = false // 是否在多边形内
    cachePointsArr = []
    options.roomAddable = val
    canvas.style.pointerEvents = val ? 'all' : 'none'
  }
  const resize = () => {
    w = Container.getBoundingClientRect().width
    h = Container.getBoundingClientRect().height
    canvasRes.width = w
    canvasRes.height = h
    if (canvas) {
      canvas.width = w
      canvas.height = h
    }
  }
  return {
    init,
    destroy,
    getPoints,
    getCtxRes,
    ctxResClear,
    drawAreaByPoints,
    updateAddable,
    resize
  }
}

// 检查图形有没有横穿
function checkPointCross(point, pointList) {
  let i
  if (pointList.length < 3) {
    return true
  }
  for (i = 0; i < pointList.length - 2; ++i) {
    const re = isPointCross(
      pointList[i],
      pointList[i + 1],
      pointList[pointList.length - 1],
      point
    )
    if (re) {
      return false
    }
  }
  return true
}

// 辅助函数 检查两个线是否交叉
function isPointCross(line1P1, line1P2, line2P1, line2P2) {
  const euqal =
   isEuqalPoint(line1P1, line2P1) ||
   isEuqalPoint(line1P1, line2P2) ||
   isEuqalPoint(line1P2, line2P1) ||
   isEuqalPoint(line1P2, line2P2)
  const re1 = isDirection(line1P1, line1P2, line2P1)
  const re2 = isDirection(line1P1, line1P2, line2P2)
  const re3 = isDirection(line2P1, line2P2, line1P1)
  const re4 = isDirection(line2P1, line2P2, line1P2)
  const re11 = re1 * re2
  const re22 = re3 * re4
  if (re11 < 0 && re22 < 0) return true
  if (euqal) {
    if (re1 === 0 && re2 === 0 && re3 === 0 && re4 === 0) return true
  } else {
    if (re11 * re22 === 0) return true
  }
  return false
}

// 辅助函数 判断两个点是否是同一个
function isEuqalPoint(point1, point2) {
  if (point1.x === point2.x && point1.y === point2.y) {
    return true
  }
}

// 辅助函数 检查第二条线的方向在第一条线的左还是右
function isDirection(point1, point2, point3) {
  // 假设point1是原点
  const p1 = getPointLine(point1, point2)
  const p2 = getPointLine(point1, point3)
  return crossLine(p1, p2)
}

// 辅助函数 两线叉乘 两线的起点必须一致
function crossLine(point1, point2) {
  return point1.x * point2.y - point2.x * point1.y
}

// 辅助函数 获取以point1作为原点的线
function getPointLine(point1, point2) {
  const p1 = {
    x: point2.x - point1.x,
    y: point2.y - point1.y
  }
  return p1
}

// 检查是否是凹图形
function checkPointConcave(point, pointList, isEnd) {
//   if (this.concaveAllow) return true
  let i
  if (pointList.length < 3) {
    return true
  }
  if (
    isPointConcave(
      pointList[pointList.length - 3],
      pointList[pointList.length - 2],
      pointList[pointList.length - 1],
      point
    )
  ) return false

  // 如果是闭合时，point为起始点，需要再判断最后两条线与第一条线是否形成凹图形
  if (isEnd) {
    if (
      isPointConcave(
        pointList[pointList.length - 2],
        pointList[pointList.length - 1],
        pointList[0],
        pointList[1]
      )
    ) return false
    if (
      isPointConcave(
        pointList[pointList.length - 1],
        pointList[0],
        pointList[1],
        pointList[2]
      )
    ) return false
  }
  return true
}

// 辅助函数 检查三个线是否凹凸
function isPointConcave(point1, point2, point3, point4) {
  const re1 = isDirection(point1, point2, point3)
  const re2 = isDirection(point2, point3, point4)
  if (re1 * re2 <= 0) return true
  return false
}

export default useDraw
